/*
 * File     : X86_64WindowsNativeCallFactory.java
 * Created  : 21 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.call;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.internal.library.DynamicLibrary;
import com.googlecode.dni.type.Pointer;


/**
 * <p>
 *  Implementation of {@link NativeCallFactory} that produces native methods for
 *  calling X86_64 Windows methods from X86_64 Windows.
 * </p>
 *
 * @author Matthew Wilson
 */
public final class X86_64WindowsNativeCallFactory extends BaseNativeCallFactory
{

    private static final DynamicLibrary LIBRARY;

    /** JNI function for one parameter. */
    private static final Pointer CALL_1;

    /** JNI function for two parameters. */
    private static final Pointer CALL_2;

    /** JNI function for three parameters. */
    private static final Pointer CALL_3;

    /** JNI function for four parameters. */
    private static final Pointer CALL_4;

    /** JNI function for more than four parameters. */
    private static final Pointer CALL_DISPLACE_4;

    /** JNI function for 'n' parameters. */
    private static final Pointer CALL_N;

    private static final Pointer[] CALLS = new Pointer[ 5 ];


    static
    {
        LIBRARY = DynamicLibrary.open( "dni.dll" );
        try
        {
            CALL_1 = LIBRARY.getSymbol( "dni_x86_64_windows_call_1" );
            CALL_2 = LIBRARY.getSymbol( "dni_x86_64_windows_call_2" );
            CALL_3 = LIBRARY.getSymbol( "dni_x86_64_windows_call_3" );
            CALL_4 = LIBRARY.getSymbol( "dni_x86_64_windows_call_4" );
            CALL_DISPLACE_4 = LIBRARY.getSymbol( "dni_x86_64_windows_call_displace_4" );
            CALL_N = LIBRARY.getSymbol( "dni_x86_64_windows_call_n" );
        }
        catch ( Throwable throwable )
        {
            try
            {
                LIBRARY.free();
            }
            catch ( Throwable throwable2 )
            {
                DniInternal.LOGGER.log( Level.SEVERE, "Failed to free library", throwable2 );
            }
            throw new ExceptionInInitializerError( throwable );
        }

        CALLS[ 1 ] = CALL_1;
        CALLS[ 2 ] = CALL_2;
        CALLS[ 3 ] = CALL_3;
        CALLS[ 4 ] = CALL_4;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    Pointer getNativeBinding( final List< Parameter > returnParameterContainer,
                              final List< Parameter > parameters,
                              final Pointer functionPointer )
    {
        int count = parameters.size();

        Pointer nativeBinding;
        if ( count == 0 )
        {
            // no mangling of parameters to be done;
            // actually point directly to native function (and not pass function
            // pointer as a parameter!)
            nativeBinding = functionPointer;
        }
        else if ( count <= 4 )
        {
            nativeBinding = CALLS[ count ];

            // have to pass the function pointer
            parameters.add( 0, new FunctionPointerParameter( functionPointer ) );
        }
        else if ( count <= 128 )
        {
            nativeBinding = getNativeBindingDisplaced( parameters, functionPointer );
        }
        else
        {
            nativeBinding = getNativeBindingLarge( parameters, functionPointer );
        }

        return nativeBinding;
    }

    /**
     * Reorders the parameters for the case with more than four 64-bit
     * parameters.
     *
     * @param parameters
     *            all parameters
     * @param functionPointer
     *            the function pointer
     *
     * @return the native binding
     */
    private Pointer getNativeBindingDisplaced( final List< Parameter > parameters,
                                               final Pointer functionPointer )
    {
        // find the first four
        List< Parameter > firstFour = new ArrayList< Parameter >( parameters.subList( 0, 4 ) );

        // move them to the back
        parameters.removeAll( firstFour );

        // calculate where they live on the stack
        // (40 bytes taken by return address + space reserved for register arguments)
        int displacementOffset = 40 + parameters.size() * 8;

        parameters.addAll( firstFour );

        // have to pass the function pointer
        parameters.add( 0, new FunctionPointerParameter( functionPointer ) );

        // add the offset
        parameters.add( 1, new ConstantIntegerParameter( displacementOffset ) );

        return CALL_DISPLACE_4;
    }

    /**
     * Uses a temporary buffer for passing the arguments.
     *
     * @param parameters
     *            the parameters
     * @param functionPointer
     *            the function pointer
     *
     * @return the native binding
     */
    private Pointer getNativeBindingLarge( final List< Parameter > parameters,
                                           final Pointer functionPointer )
    {
        // use a 'large parameter' to pass in a pointer to the args
        LargeParameter64 largeParameter = new LargeParameter64( parameters );
        parameters.clear();

        // have to pass the function pointer
        parameters.add( new FunctionPointerParameter( functionPointer ) );

        // pass in the pointer to the arguments
        parameters.add( largeParameter );

        // pass in the count of arguments
        parameters.add( new ConstantIntegerParameter( largeParameter.getWords() ) );

        return CALL_N;
    }

}
